import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import javax.media.j3d.Appearance;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.LineStripArray;
import javax.media.j3d.Node;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.WindowConstants;

import com.sun.j3d.utils.universe.SimpleUniverse;

/*
 * Computational Worlds Final Project
 * Edited By: Pritpal Singh, Patrick Black
 */
public class PongDemo {
	// Physics updates per second (approximate).
	private static final int UPDATE_RATE = 30;
	// Number of full iterations of the collision detection and resolution
	// system.
	private static final int COLLISION_ITERATIONS = 4;
	// Width of the extent in meters.
	private static final float EXTENT_WIDTH = 20;

	// Normal boundaries that cause collisions
	private final HalfSpace[] boundaries;
	// Special boundaries that may result in object migration
	private final HalfSpace leftBoundary;
	private final HalfSpace rightBoundary;
	private final LinkedList<PhysicsObject> objects;
	private Peer peer;
	private SimpleUniverse simpleU;
	private BranchGroup scene;
	private BranchGroup trueScene;
	JFrame appFrame;
	JTextField scoreField;
	boolean gameOver = false;
	Random random = new Random();
	Rectangle paddle;

	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				JOptionPane.showMessageDialog(null, helpMessage());
				new PongDemo().createAndShowGUI();
			}
		});
	}

	public PongDemo() {
		peer = new Peer();
		peer.setup();

		boundaries = new HalfSpace[] {
				new HalfSpace(-EXTENT_WIDTH / 2, -EXTENT_WIDTH / 2, 0, 1),
				new HalfSpace(EXTENT_WIDTH / 2, EXTENT_WIDTH / 2, 0, -1) };
		leftBoundary = new HalfSpace(-EXTENT_WIDTH / 2, -EXTENT_WIDTH / 2, 1, 0);
		rightBoundary = new HalfSpace(EXTENT_WIDTH / 2, EXTENT_WIDTH / 2, -1, 0);
		objects = new LinkedList<PhysicsObject>();
		PhysicsObject o = newObject();

		objects.add(o);
		paddle = new Rectangle(1, EXTENT_WIDTH - 13, 0, 0, 0,
				(float) (Math.PI * 0.5f), 0, EXTENT_WIDTH * .3f,
				EXTENT_WIDTH * .1f, null);
		objects.add(paddle);

	}

	private PhysicsObject newObject() {
		PhysicsObject o = null;
		int nextObject = random.nextInt(2);
		
		if (nextObject == 0) {
			o = (new Square(1, (float) (Math.random() - 0.5) * EXTENT_WIDTH,
					(float) (Math.random() - .5) * EXTENT_WIDTH, -4.0f, 5.0f,
					(float) (2 * Math.PI * Math.random()), 0,
					EXTENT_WIDTH * .1f, null));
		} else {
			o = (new Triangle(1, (float) (Math.random() - 0.5) * EXTENT_WIDTH,
					(float) (Math.random() - .5) * EXTENT_WIDTH, -4.0f, 5.0f,
					(float) (2 * Math.PI * Math.random()), 0,
					EXTENT_WIDTH * .1f, null, true));
		} 
		
		//random.setSeed(System.currentTimeMillis());
		nextObject = random.nextInt(10);
		
		if(nextObject != 0 && nextObject % 3 == 0) {
			o = (new Circle(1, (float) (Math.random() - 0.5) * EXTENT_WIDTH,
					(float) (Math.random() - .5) * EXTENT_WIDTH, -4.0f, 5.0f,
					0, 0, EXTENT_WIDTH * .03f, null));
		}
		
		return o;
	}

	private void createAndShowGUI() {
		// Fix for background flickering on some platforms
		System.setProperty("sun.awt.noerasebackground", "true");

		GraphicsConfiguration config = SimpleUniverse
				.getPreferredConfiguration();
		final Canvas3D canvas3D = new Canvas3D(config);
		simpleU = new SimpleUniverse(canvas3D);
		simpleU.getViewingPlatform().setNominalViewingTransform();

		// Add a scaling transform that resizes the virtual world to fit
		// within the standard view frustum.
		trueScene = new BranchGroup();
		trueScene.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
		trueScene.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		trueScene.setCapability(BranchGroup.ALLOW_DETACH);
		TransformGroup worldScaleTG = new TransformGroup();
		Transform3D t3D = new Transform3D();
		t3D.setScale(.9 / EXTENT_WIDTH);
		worldScaleTG.setTransform(t3D);
		trueScene.addChild(worldScaleTG);
		scene = new BranchGroup();
		scene.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
		scene.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		scene.setCapability(BranchGroup.ALLOW_DETACH);
		worldScaleTG.addChild(scene);

		final TransformGroup extentTransform = new TransformGroup();
		extentTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		extentTransform.addChild(createExtent());
		scene.addChild(extentTransform);
		for (PhysicsObject o : objects)
			scene.addChild(o.BG);

		// paddle mouse control
		canvas3D.addMouseMotionListener(new MouseMotionAdapter() {
			private MouseEvent lastDragEvent;

			public void mouseDragged(MouseEvent e) {
				if (lastDragEvent != null) {

					float x = -(e.getY() - lastDragEvent.getY())
							+ paddle.position.y;
					if (x < 5 && x > -11)
						paddle.position.y = x;
					paddle.updateTransformGroup();

				}
				lastDragEvent = e;
			}

			public void mouseMoved(MouseEvent e) {
				lastDragEvent = null;
			}
		});

		simpleU.addBranchGraph(trueScene);

		appFrame = new JFrame("Pong");
		appFrame.setLayout(new BorderLayout());
		scoreField = new JTextField("Points: " + peer.getScore());
		scoreField.setEditable(false);
		scoreField.setHorizontalAlignment(JTextField.CENTER);
		Font font = new Font("Verdana", Font.BOLD, 40);
		scoreField.setFont(font);
		scoreField.setForeground(Color.BLUE);
		appFrame.add(scoreField, BorderLayout.NORTH);
		appFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		appFrame.add(canvas3D);
		appFrame.pack();
		if (Toolkit.getDefaultToolkit().isFrameStateSupported(
				JFrame.MAXIMIZED_BOTH))
			appFrame.setExtendedState(appFrame.getExtendedState()
					| JFrame.MAXIMIZED_BOTH);

		new Timer(1000 / UPDATE_RATE, new ActionListener() {
			public void actionPerformed(ActionEvent e) {

				canvas3D.stopRenderer();
				tick();
				canvas3D.startRenderer();
			}

		}).start();

		appFrame.setVisible(true);
	}

	private void tick() {
		List<PhysicsObject> received = peer.getReceived();

		if (received != null)
			for (PhysicsObject original : received) {
				PhysicsObject o = newObject();

				o.velocity = original.velocity;
				o.position = original.position;

				o.position.x = o.position.x;
				o.velocity.x = 10.0f;
				o.updateTransformGroup();
				scene.addChild(o.BG);
				objects.add(o);
			}
		for (PhysicsObject o : objects) {
			o.updateState(1f / UPDATE_RATE);
		}
		for (int i = 0; i < COLLISION_ITERATIONS; i++) {
			Iterator<PhysicsObject> itr = objects.iterator();
			while (itr.hasNext()) {
				PhysicsObject o = itr.next();

				for (HalfSpace hs : boundaries)
					CollisionHandler.checkAndResolveCollision(hs, o);
				for (PhysicsObject o2 : objects) {
					CollisionHandler.checkAndResolveCollision(o2, o);
				}
				CollisionInfo ci = CollisionHandler.getCollisionInfo(
						leftBoundary, o);
				if (ci != null) {

					if (peer.sendPhysicsObject(o, random.nextInt(1024))) {
						o.resetScored();
						o.BG.detach();
						itr.remove();
						continue;
					} else
						CollisionHandler.resolveCollision(leftBoundary, o, ci);
				}
				ci = CollisionHandler.getCollisionInfo(rightBoundary, o);
				if (ci != null) {

					o.position.x = 0.0f;
					o.position.y = 0.0f;
					o.velocity.x = -10.0f;
					o.velocity.y = 0.0f;
					if (peer.getScore() == 10) {
						peer.disconnectFromNetwork();
						gameOver = true;
						scoreField.setText("GAME OVER!");

					} else if (!o.scored()) {
						if (o instanceof Circle) {
							o.score();
							if (!o.collidedPaddle())
								peer.decrementScore();
							scoreField.setText("Points: " + peer.getScore());
						} else {
							o.score();
							peer.incrementScore();
							scoreField.setText("Points: " + peer.getScore());
						}

					}

				}
			}
		}
		for (PhysicsObject o : objects) {
			o.updateTransformGroup();
			// Clear the object's force accumulator.
			o.forceAccumulator.x = o.forceAccumulator.y = 0;
		}
	}

	private static Node createExtent() {
		float[] coordinates = { -EXTENT_WIDTH / 2, -EXTENT_WIDTH / 2, 0,
				EXTENT_WIDTH / 2, -EXTENT_WIDTH / 2, 0, EXTENT_WIDTH / 2,
				EXTENT_WIDTH / 2, 0, -EXTENT_WIDTH / 2, EXTENT_WIDTH / 2, 0,
				-EXTENT_WIDTH / 2, -EXTENT_WIDTH / 2, 0 };
		LineStripArray geometry = new LineStripArray(5,
				GeometryArray.COORDINATES, new int[] { 5 });

		geometry.setCoordinates(0, coordinates);
		Shape3D shape = new Shape3D(geometry);
		Appearance appearance = new Appearance();
		appearance.setColoringAttributes(new ColoringAttributes(1f, 1f, 1f,
				ColoringAttributes.FASTEST));
		shape.setAppearance(appearance);

		return shape;
	}

	private static String helpMessage() {
		final StringBuilder sb = new StringBuilder();
		sb.append("Things to Know:\n");
		sb.append("- Mission: Keep the score below 10 to stay in the game.\n");
		sb.append("- Drag the mouse to move the paddle.\n");
		sb.append("- Block Squares and Triangles to keep the score below 10.\n");
		sb.append("- Avoid blocking Circles to earn back lost points.\n");
		sb.append("- If the game is over, you have lost and disconnected.\n");
		sb.append("\nPlease choose an option from the console to start the game.");

		return sb.toString();
	}

}